
# Copyright 2009 David Lee Miller
#
# This file is part of POSSM (Python Open Source Scanning Microscopy).
#
# POSSM is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# POSSM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License         
# along with POSSM.  If not, see <http://www.gnu.org/licenses/>.
#                                                                           
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

from controller import cPickle,os,time,n,ws
from copy import copy
import random as ra

#This file implements the low level functions of the DAC board
#into useful functions for use with the STM. If you wish to use
#a different driver with the software, simply recreate the classes
#and functions in the driver interface. Then create an instance of
#your new class in the socketdaemon and pass them to the classes which
#are defined below.

class SaveDAC(object):
	'''
	Stores the current dac values for all of the
	analog outputs.
	'''
	location = os.environ.get('HOME')
	current_dac = {}
	
	def saveDAC(self,location):
		'''
		Saves the current value of the analog output channel
		to file.
		
		'''
		
		fh = open(location+'/current_values','w')
		cPickle.dump(current_dac,fh)
		fh.close()

savepos = SaveDAC()

class DigitalOutput(object):
	'A class for digital output'
	bits = []
	
	def __init__(self,device,reset=True,saveclass=savepos):
		if device.SUB_DIO == None: raise 'Device does not have DIO capabilities'
		else: self.device = device
		self.saveclass = saveclass
		for i in range(self.device.DIO_BITS):
			self.device.DigitalConfig(i)
		if reset:
			self.clrBit(-1)
		for i in range(self.device.DIO_BITS):
			self.bits.append(0)
	
	def setBit(self,bit):
		'''
		Sets the selected bit to the high value on the digital
		output port. If bit is already set, nothing changes.
		
		Keyword Arguments:
		bit -- the bit (0-7) to set to high
		
		'''
		
		if type(bit) != int and not -1<=bit<=self.device.DIO_BITS-1: raise 'invalid bit!'
		if bit < 0:
			pass
		elif 0 <= bit <= self.device.DIO_BITS-1:
			self.bits[bit] = 1
			self.device.DIOWrite(bit,1)
	
	def clrBit(self,bit):
		'''
		Sets the selected bit to the high value on the digital
		output port. If bit is already set, nothing changes.
		
		Keyword Arguments:
		bit -- the bit (0-7) to set to high
		
		'''
		
		if type(bit) != int and not -1<=bit<=self.device.DIO_BITS-1: raise 'invalid bit!'
		if bit < 0:
			self.bits = []
			for i in range(self.device.DIO_BITS):
				self.device.DIOWrite(i,0)
				self.bits.append(0)
		elif 0 <= bit <= self.device.DIO_BITS-1:
			self.bits[bit] = 1
			self.device.DIOWrite(bit,0)
	
	def setBits(self,bits):
		'''
		Sets the selected bits to the specified values on the digital
		output port. If a bit is already set, nothing changes.
		
		Keyword Arguments:
		bits -- [(bit#,value),(bit#,value),...]
		
		'''
		
		if type(bits) != list: raise 'Invalid type for bits'
		for i in range(len(bits)):
			if 0 <= bits[i][0] <= self.device.DIO_BITS-1:
				self.bits[bits[i][0]] = bits[i][1]
				self.device.DIOWrite(bits[i][0],bits[i][1])
	
	def microdelay(self,ticks):
		'''
		Software timer to delay "ticks" microseconds. Accuracy to less 
		than +-4 microseconds barring system delays.
		
		Keyword Arguments:
		ticks -- Number of 4 microsecond ticks to delay
		
		'''
		smallest_delay = 0.000004
		total_time = 0
		if ticks < 250:
			start_time = time.time()
			while total_time < smallest_delay*ticks:
				total_time = time.time() - start_time
		else:
			mil = ticks/250
			start_time = time.time()
			time.sleep(mil/1000.)
			while total_time < smallest_delay*ticks:
				total_time = time.time() - start_time
	
	def digTrig(self,num,chan):
		'''
		Sends digital triggers.
		
		Keyword arguments:
		num -- the number of triggers to send
		chan -- the integer channel number to output on
		
		First function argument is the number of digital triggers to
		output. Outputs to the channel specified in the second function
		argument.
		
		'''
	
		for i in range(num):
			self.device.DIOWrite(bit,1)
			self.microdelay(3)
			self.device.DIOWrite(bit,0)


class AnalogOutput(object):
	'Talks to a device object and implements its functions'
	
	rng = 0  #the range to use with the device
	
	def __init__(self,device,channel,aoid,reset=True,saveclass=savepos):
		if device.SUB_AO == None: raise 'Device does not have AO capabilities'
		else: self.device = device
		self.channel = channel
		self.saveclass = saveclass
		self.aoid = aoid  #the id name for reference purposes
		if reset:
			self.setVolts(0)
			self.current_dac = device.PhysicalToInteger(device.SUB_AO,channel,self.rng,0)
	
	def microdelay(self,ticks):
		'''
		Software timer to delay "ticks" microseconds. Accuracy to less 
		than +-4 microseconds barring system delays.
		
		Keyword Arguments:
		ticks -- Number of 4 microsecond ticks to delay
		
		'''
		smallest_delay = 0.000004
		total_time = 0
		if ticks < 250:
			start_time = time.time()
			while total_time < smallest_delay*ticks:
				total_time = time.time() - start_time
		else:
			mil = ticks/250
			start_time = time.time()
			time.sleep(mil/1000.)
			while total_time < smallest_delay*ticks:
				total_time = time.time() - start_time
	
	def loadDAC(self):
		'''
		Loads the current value of the analog output channel to
		the class.
		
		'''
		
		fh = open(self.saveclass.location+'/current_dac','r')
		params = cPickle.load(fh)
		self.current_dac = current_dac[self.aoid]
		fh.close()
	
	def setDAC(self):
		'''
		Updates the current dac value in the SaveDAC class.
		
		'''
		
		self.saveclass.current_dac[self.aoid] = self.current_dac
	
		
	def writeSteps(self,nsteps,step,delay=0):
		'''
		Writes nsteps*step from the current location in an integer ramp.
		
		Keyword Arguments:
		nsteps -- number of steps to take
		step -- step size in digital units
		delay -- the delay time in microsleeps
		'''
		direction = 1
		for i in range(abs(nsteps)):
			self.current_dac = self.device.AOInteger(self.channel,self.rng,self.current_dac + direction*step)
	
	def writeSingle(self,dac,delay=0,rate=0):
		'''
		Writes a single voltage value to the board.
		
		'''
		
		self.current_dac = self.device.AOInteger(self.channel,self.rng,dac)
		self.setDAC()
		if rate:
			period = (1./rate)*1e6
		else: pass
	
	def rampVolts(self,end,step,rate=0):
		'''
		Makes the ramp, converts it to integers and ouputs it on
		board.
		
		rate -- the rate in hz to ramp the dac
		'''
		if rate: period = (1./rate)*1e6  #in us
		else: period = 0
		endint = self.device.PhysicalToInteger(self.device.SUB_AO,self.channel,self.rng,end)
		nsteps = abs(endint - self.current_dac)/step
		if endint < self.current_dac: nsteps *= -1
		if not abs(nsteps) < 1: self.writeSteps(nsteps,step,delay=(period/4.))
		#rem = endint - self.current_dac
		#if endint < self.current_dac: rem *= -1
		if endint != self.current_dac: #self.writeSteps(rem,1,delay=(period/4.))
			self.writeSingle(endint)
	
	def setVolts(self,val,rate=0):
		'''
		Steps the voltage to val.
		
		'''
		if rate: period = (1./rate)*1e6  #in us
		else: period = 0
		
		intval = self.device.PhysicalToInteger(self.device.SUB_AO,self.channel,self.rng,val)
		self.writeSingle(intval,delay=(period/4.))
	
	def currentPhysical(self):
		'''
		Returns the value of current_dac in physical units.
		'''
		val = self.device.IntegerToPhysical(self.device.SUB_AO,self.channel,self.rng,self.current_dac)
		return val
	
	def convertToPhysical(self,dac):
		val = self.device.IntegerToPhysical(self.device.SUB_AO,self.channel,self.rng,dac)
		return val
	
	def convertToInteger(self,val):
		'''
		Steps the voltage to val.
		
		'''
		
		intval = self.device.PhysicalToInteger(self.device.SUB_AO,self.channel,self.rng,val)
		return intval



class AnalogInput(object):
	'An implementation of the analog input device.'
	
	rng = 0 #the sampling range to use
	
	def __init__(self,device,channel):
		if device.SUB_AI == None: raise 'Device does not have AI capabilities'
		else: self.device = device
		self.channel = channel
	
	def readSingle(self):
		'''
		Reads a single integer off of the board and converts it to a voltage.
		
		'''
		
		intval = self.device.AIInteger(self.chan,self.rng)
		val = self.device.IntegerToPhysical(self.device.SUB_AI,self.channel,self.rng,intval)
		return val
	
	def readFew(self,nscans,ignore=0):
		'''
		Takes n samples on a channel, averages them.
		
		Keyword Arguments:
		nscans -- the number of points to sample prior to averaging
		
		'''
		physval = (ra.random()-0.5)*20
		return physval
		
	def convertToPhysical(self,dac):
		val = self.device.IntegerToPhysical(self.device.SUB_AO,self.channel,self.rng,dac)
		return val
	
	def convertToInteger(self,val):
		'''
		Steps the voltage to val.
		
		'''
		
		intval = self.device.PhysicalToInteger(self.device.SUB_AO,self.channel,self.rng,val)
		return intval


class AnalogIO(AnalogOutput,AnalogInput):
	
	wrng = 0
	rrng = 0
	
	def __init__(self,rdevice,wdevice,rchannel,wchannel,aoid,reset=True,saveclass=savepos):
		if rdevice.SUB_AI == None or wdevice.SUB_AO == None: pass  #errors will be printed by AO and AI
		else:
                    self.device = wdevice
                    self.wdevice = wdevice
                    self.rdevice = rdevice
		self.wchannel = wchannel
		self.rchannel = rchannel
		AnalogOutput.__init__(self,wdevice,wchannel,aoid,reset=reset,saveclass=saveclass)
		AnalogInput.__init__(self,rdevice,rchannel)
		
	def rwScan(self,nsteps,sleep_wave,direction,navgs,step,reads,**kwargs):
		'''
		Steps the DAC from position start by the number of steps
		in the given direction and reads a value at each step.
		
		'''
		#if not start: start = self.current_dac
		count = 0
		avg = 0
		avgcnt = 0
		avg_lst = []
		data = []
		for i in range(nsteps):
			if i < 0: i = 0
			elif i > (2**self.device.AO_BITS-1): i = (2**self.device.AO_BITS-1)
			val = self.readFew(reads)
			avg_lst.append(val)
			if len(avg_lst) == navgs:
				data.append(avg_lst)
				avg_lst = []
			else: pass
		data = n.mean(data,1).tolist()
		wdata = copy(data)
		if direction == 1:
			if 'retw' in kwargs and kwargs['retw']: return data,wdata
			else: return data
		else:
			if 'retw' in kwargs and kwargs['retw']: return n.flipud(data),n.flipud(wdata)
			else: return n.flipud(data)
			